必知的HTTP协议

# 必知的HTTP协议

[TOC]

# 一、HTTP协议的主要特点

# 1.1 简单快速

每个资源(比如图片、页面)都通过 url 来定位。这都是固定的,在http协议中,处理起来也比较简单,想访问什么资源,直接输入url即可。

# 1.2 灵活

http协议的头部有一个数据类型,通过http协议,就可以完成不同数据类型的传输。

# 1.3 无连接

连接一次,就会断开,不会继续保持连接。

# 1.4 无状态

客户端和服务器端是两种身份。第一次请求结束后,就断开了,第二次请求时,服务器端并没有记住之前的状态,也就是说,服务器端无法区分客户端是否为同一个人、同一个身份。

有的时候,我们访问网站时,网站能记住我们的账号,这个是通过其他的手段(比如 session)做到的,并不是http协议能做到的。

# 二、HTTP报文的组成部分

报文就是浏览器和服务器之间传输的东西。

报文头<=32k,报文体<=2G。

# 2.1 请求报文

img

# 2.1.1 请求行

包含请求方法、请求的url(首页简写成/、http协议及版本。

# 2.1.2 请求头

一大堆的键值对,告诉服务端需要哪些内容,要什么数据类型。包含一些客户端环境信息,身份验证信息等。

# 2.1.3 空行

当服务器在解析请求头的时候,如果遇到了空行,则表明,后面的内容是请求体。

# 2.1.4 请求体

数据部分。包含要发送的一些字符串信息,表单信息等。

# 2.2 响应报文

img

# 2.2.1 状态行

报文协议及版本、状态码及状态描述。

# 2.2.2 响应头

包含服务器类型、日期时间、内容类型和长度等信息。

# 2.2.3 空行

# 2.2.4 响应体

# 三、HTTP方法

# 3.1 HTTP1.0 方法

# 3.1.1 GET:获取资源

# 3.1.1.1 GET
  • GET方法的数据是放在URL里的参数来传输的。

  • 对发送信息的数量一般限制在2000字符。

  • 默认方式,一般用于查询、获取操作。

  • 不是很安全,任何人可见,信息都显示在URL中 。

【幂等】:一个操作任意多次执行所产生的影响均与一次执行的影响相同。GET请求就是一种幂等操作。

  • 在URI后使用”#“,就可以获取页面后直接定位到某个标签所在的位置。
  • 以 " ? "分割url和传输数据,多个参数用 "&"连接。
# 3.1.1.2 表单GET提交
<!-- form_get.html -->
<form action="http://localhost:8080/aaa" method="get">
    用户:<input type="text" name="username" /><br>
    密码:<input type="password" name="password" /><br>
    <input type="submit" value="提交" />
</form>

<!-- 提交后 -> http://localhost:8080/aaa?username=123&password=aaa -->
1
2
3
4
5
6
7
8
// server_get1.js
const http=require('http');
const querystring=require('querystring');

let server=http.createServer(function (req, res){
    // 在?处切一刀
    let [url, query]=req.url.split('?');
    // 解析url参数
    let get=querystring.parse(query);

    console.log(url, get);
	// /aaa [Object: null prototype] { username: '123', password: 'aaa' }
});
server.listen(8080);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// server_get2.js
const http=require('http');
const url=require('url');

let server=http.createServer(function (req, res){
    // 加true是为将query由字符串转换成对象形式
    let {pathname, query}=url.parse(req.url, true);

    console.log(pathname, query);
    // 无true时:/aaa username=123&password=aaa
    // /aaa [Object: null prototype] { username: '123', password: 'aaa' }
});
server.listen(8080);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.1.2 POST:提交数据

# 3.1.2.1 POST

一般用于修改服务器上的资源,对所发送的数量无限制。一般用于发送表单数据,新建,修改,删除等操作,要安全一些,不在URL中显示,对其他人不显示。

  • 根据传输的数据大小来决定分多少段,分多少次来传输。
  • 非幂等性。
# 3.1.2.2 表单POST提交
// server_post.js
const http=require('http');
const querystring=require('querystring');

let server=http.createServer(function (req, res){
    let arr=[];
    req.on('data', buffer=>{
        arr.push(buffer);
    });
    req.on('end', ()=>{
        let buffer=Buffer.concat(arr);
        // 一般情况下不可以toString
        // 比如传输的数据是个视频而不是纯粹的字符串
        let post=querystring.parse(buffer.toString());

        console.log(post);
        // [Object: null prototype] { username: '123', password: 'aaa' }
    });
});
server.listen(8080);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 3.1.2.3 GET和POST的区别

GET是相对不隐私的,而POST是相对隐私的

  1. 浏览器在回退时,GET不会重新请求,但是POST会重新请求。
  2. GET产生的URL地址可以被收藏,而POST不可以。
  3. GET请求会被浏览器主动缓存,而POST不会,除非手动设置。
  4. GET请求只能进行url编码,而POST支持多种编码方式。
  5. **GET请求的参数,会报保留在浏览器的历史记录里,而POST不会。**做业务时要注意。为了防止CSRF攻击,很多公司把GET统一改成了POST
  6. GET请求在url中传递的参数有大小限制,基本是2kb,不同的浏览器略有不同(真正起到限制的是浏览器对其长度进行了限制),而POST没有限制。超出的长度会被截断。
  7. 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  8. GET的参数是直接暴露在url上的,相对不安全。而POST是放在请求体中的。

首先最直观的区别就是GET把参数暴露在url上,而POST是通过请求体传递参数的。

然而get和post在本质上都是tcp连接,但由于受http协议的规范,以及浏览器和服务器的限制,从而使它们在实际应用上表现出了差别。

第一点,get在请求时发送一个数据包,会将header和data一起发送过去。而post会产生两个数据包,先发送header,服务器返回100continue,然后再发送data,服务器返回200ok。

第二点,在安全上,get是只读操作,不会对服务器上的资源造成实质的修改,是安全的。而post操作会修改服务器上的资源,就相对会不安全。

第三点,在幂等上,GET多次执行相同的操作,结果也是相同的,是幂等的。而POST多次提交数据会创建多个资源,是不幂等的。

第四点,在缓存上,GET请求是可以被浏览器主动缓存,而大多浏览器是不支持POST缓存的。

# 3.1.3HEAD:获得报文首部

# 3.2 HTTP1.1 方法

# 3.2.1 PUT:整体更新资源

  • 幂等性。
  • 自身不带验证,存在安全性问题。

# 3.2.2 PATCH:局部更新资源。

  • PUT的补充方法。

# 3.2.3 DELETE:删除资源

# 3.2.4 TRACE:追踪路径

  • 存在安全性问题,不常用。

# 3.2.5 CONNECT:要求用隧道协议连接代理

# 3.2.6 OPTIONS:询问支持的方法

# 四、HTTP状态码

# 4.1 1XX:Informational信息性

接收的请求正在处理。

# 4.1.1 100 Continue

继续,一般在发送post请求时,已发送了http header之后,服务端将返回此信息,表示确认,之后发送具体参数信息。

# 4.2 2XX:Success成功

请求正常处理完毕。

# 4.2.1 200 OK

客户端请求成功。

# 4.2.2 204 No Content

请求处理成功,但没有资源可返回。

# 4.2.3 206 Partial Content

对资源某一部分的请求。

客户发送了一个带有Range头的POST请求,服务器完成了它。

range指的是请求的范围,客户端只请求某个大文件里的一部分内容。比如说,如果播放视频地址或音频地址的前面一部分,可以用到206。

# 4.3 3XX:Redirection重定向

需要进行附加操作以完成请求。

# 4.3.1 301 Moved Permanently

永久重定向,所请求的页面已经转移至新的url。

# 4.3.2 302 Found

临时重定向,所请求的页面已经临时转移至新的url。

# 4.3.3 303 See Other

303 状态码和 302 Found 状态码有着相同的功能, 但 303 状态码明确表示客户端应当采用 GET 方法获取资源, 这点与 302 状态码有区别。

# 4.3.4 304 Not Modified

服务器资源未变化。

客户端有缓冲的文档并发出了一个条件性的请求,服务器告诉客户,原来缓冲的文档还可以继续使用。

# 4.4 4XX:Client Error客户端错误

服务器无法处理请求。

# 4.4.1 400 Bad Request

客户端请求有语法错误,不能被服务器所理解。

# 4.4.2 401 Underauthorized

请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。

# 4.4.3 403 Forbidden

访问被禁止。

# 4.4.4 404 Not Found

请求资源不存在。

# 4.5 5XX:Server Error服务器错误

服务器处理请求出错。

# 4.5.1 500 Internal Server Error

服务器发生不可预期的错误,原来缓冲的文档还可以继续使用。

# 4.5.2 502 Bad Gateway

错误网关,无效网关。这通常并不意味着上游服务器已关闭(无响应网关(代理) ,而是上游服务器和网关/代理使用不一致的协议交换数据。

# 4.5.2 503 Server Unavailable

请求未完成,服务器临时过载或宕机(死机),一段时间后可能恢复正常。

# 五、持久连接/长连接

  • 轮询http1.0中,采用“请求-应答”模式,每个请求/应答,客户和服务器都要新建一个连接,完成之后立即断开连接。
  • 长连接HTTP1.1中,通过使用Connection:keep-alive进行长连接,。客户端只请求一次,但是服务器会将继续保持连接,当再次请求时,避免了重新建立连接。

注意,HTTP 1.1默认进行持久连接。在一次 TCP 连接中可以完成多个 HTTP 请求,但是对每个请求仍然要单独发 header,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。

  • 旨在建立 1 次 TCP 连接后进行多次请求和响应的交互
  • 减少了 TCP 连接的重复建立和断开所造成的额外开销
  • 减轻了服务器端的负载。
  • 使HTTP 请求和响应能够更早地结束, Web 页面显示速度提高了

# 六、长连接中的管线化

管线化机制通过持久连接完成,仅HTTP/1.1支持此技术。

只有GET和HEAD请求可以进行管线化,而POST则有所限制。

能够做到同时并行发送多个请求, 而不需要一个接一个地等待响应。

# 6.1 管线化的原理

持久连接时,默认的请求这样的:

	请求1 --> 响应1 -->请求2 --> 响应2 --> 请求3 --> 响应3
1

长连接中的管线化,请求是这样的:

	请求1 --> 请求2 --> 请求3 --> 响应1 --> 响应2 --> 响应3
1

管线化就是,我把现在的请求打包,一次性发过去,你也给我一次响应回来。

# 七、一个完整的HTTP请求七步骤

# 7.1 建立TCP连接

# 7.2 浏览器向服务器发送请求命令

# 7.3 浏览器发送请求头信息

# 7.4 服务器应答

# 7.5 服务器发送应答头信息

# 7.6 服务器向浏览器发送数据

# 7.7 服务器关闭TCP连接

# 八、三次握手与四次挥手

# 8.1 三次握手

为了建立连接。

TCP的标志(flag)

  • SYN(synchronize) 同步
  • ACK(acknowledgement)确认
  • 若在握手过程中某个阶段莫名中断, TCP 协议会再次以相同的顺序发送相同的数据包。

三次握手

# 8.1.1 第一次握手

起初两端都处于CLOSED关闭状态,Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN-SENT(请求连接)状态,等待Server确认。

# 8.1.2 第二次握手

Server收到数据包后由标志位SYN=1得知Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN-RCVD状态,此时操作系统为该TCP连接分配TCP缓存和变量。

# 8.1.3 第三次握手

Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并且此时操作系统为该TCP连接分配TCP缓存和变量,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client和Server就可以开始传输数据。

简单来说,就是

1、建立连接时,客户端发送SYN包到服务器,并进入到SYN-SENT状态,等待服务器确认。

2、服务器收到SYN包,必须确认客户的SYN,同时也发送一个SYN+ACK包,并进入SYN-RECV状态。

3、客户端收到后,向服务器发送ACK确认包,客户端和服务器进入ESTABLISHED状态。

  • 为什么要三次握手呢?

    • 确保客户端和服务端都能明确自己和对方的收、发能力是正常的。
    • 第一次握手:服务器知道-客户端的发送和服务端的接收能力正常。
    • 第二次握手:客户端知道-双方的发送和接收能力正常。
    • 第三次握手:服务端知道-双方的发送和接收能力正常。
  • 为什么TCP客户端最后还要发送一次确认呢?

    • 主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

    • 如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。

    • 此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

    • 如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

# 8.2 四次挥手

为了终止连接。

四次挥手

# 8.2.1 第一次挥手

客户端进程发出连接释放报文,并且停止发送数据。设置报文FIN=1,其序列号为seq=u 客户端从ESTABLISED状态进入FIN-WAIT-1状态。

# 8.2.2 第二次挥手

服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1。seq=v。

发送后,服务器从ESTABLISHED状态,进入CLOSE-WAIT状态。

收到后,客户端从FIN-WAIT-1状态,进入FIN-WAIT-2状态。

# 8.2.3 第三次挥手

服务器将最后的数据发送完毕后,就向客户端发送连接释放报文FIN=1,ACK=1, ack=u+1,服务器又发送了一些数据后截止,序列号为seq=w。

发送后,服务器从CLOSE-WAIT状态,进入LAST_ACK状态。

# 8.2.4 第四次挥手

客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1。

发送后,客户端从FIN-WAIT-2状态,进入TIME-WAIT状态。

收到后,服务器从LAST-ACK状态,进入CLOSED状态。

简单来说,就是

1、Client发送一个FIN,用来关闭Client到Server的数据传送,并t进入FIN_WAIT_1状态。

2、Server收到后,发送一个ACK确认,并进入CLOSE_WAIT状态,Client收到后进入FIN_WAIT_2状态。

3、Server发送一个FIN+ACK包,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

4、Client收到后,发送一个ACK确认,并进入TIME_WAIT状态,Server进入CLOSED状态。

  • 等待2MSL(最大报文存活时间)后,Client也进入CLOSED状态。
  • 为什么客户端最后还要等待2MSL?

    • MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。

    • 第一,确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。

    • 第二,等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

  • 为什么建立连接是三次握手,关闭连接确是四次挥手呢?

    • 建立连接的时候, 服务器收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
    • 而关闭连接时,服务器收到 FIN 连接释放报文后,发送一个ACK确认,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器才会发送 FIN +ACK包。

# 8.3 TCP和UDP的区别

  • 可靠性
    • TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,是一个有状态的协议,需要先与对方建立连接后才能发送数据,而且保证数据不丢失不重复。
    • UDP(User Data Protocol,用户数据报协议)是与 TCP 相对应的协议。它是面向非连接的协议,无状态,不用事先建立连接就可以任意发送数据,但不保证数据一定会发到对方。
  • 数据形式
    • TCP的数据是连接的“字节流”,有先后顺序。
    • UDP是分散的小数据包,是顺序发,乱序收。

UDP 适用于视频直播,丢一帧两帧也无所谓,但是要保证视频流畅。

# 九、HTTP缓存策略

  • 资源内容是否可复用?
    • No:为Cache-Control设置no-store,拒绝一切形式的缓存。
    • Yes:是否每次都需要向服务器进行缓存有效确认?
      • Yes:将Cache-Control的值设为no-cache。
      • No:考虑该资源是否可以被代理服务器缓存;考虑该资源的过期时间,设置max-age和s-maxage值;配置协商缓存需要用到的Etag、Last-Modified参数。

# 9.1 Memory Cache 内存缓存

  • 优先级:浏览器最先尝试命中的一种缓存。
  • 效率:响应速度最快的一种缓存。
  • 生命周期:和渲染进程相依为命,tab关闭后,内存的数据也会消失。
  • 缓存对象:几乎所有的Base64格式,节省渲染开销;体积较小的JS、CSS文件。(较大的JS、CSS文件会被存进磁盘)

# 9.2 Service Worker Cache 离线缓存

  • Service Worker是一种独立于主线程外的JS线程,脱离于浏览器窗体,故无法直接访问DOM;可以用来实现离线缓存、消息推送和网络代理等功能。
  • 要求协议是Https协议。